/*
	Project:	CoreEngine
	Author:		Dino Bojadzhievski
	File:		Matrix2

	Defines a two-dimensional matrix.
	Non-generic, non-polymorphic.
	Extends nothing.

	Does not support minor and cofactor extraction (mathematically undefined for dimensions > 3).
*/

#pragma once

#include "../CoreTypes.h"

namespace CoreMath
{
	struct Matrix2
	{
		/* Data. */

		real value[2][2];
		
		/* Constructors. */

		Matrix2()
		{
			this->Identity();
		}

		Matrix2(real a00, real a01, real a10, real a11)
		{
			this->value[0][0] = a00;
			this->value[0][1] = a01;
			this->value[1][0] = a10;
			this->value[1][1] = a11;
		}

		Matrix2(const Matrix2& toCopy)
		{
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
				this->value[i][j] = toCopy.value[i][j];
		}

		/* Operators. */

		inline Matrix2 operator=(const Matrix2& toAssign)
		{
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
				this->value[i][j] = toAssign.value[i][j];
			return *this;
		}

		inline bool operator==(const Matrix2& toCompare) const
		{
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
				if (this->value[i][j] != toCompare.value[i][j])
					return false;
			return true;
		}

		inline bool operator!=(const Matrix2& toCompare) const
		{
			return !(*this == toCompare);
		}

		inline Matrix2 operator+(const Matrix2& toAdd) const
		{
			Matrix2 m_RetVal;
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
					m_RetVal.value[i][j] = this->value[i][j] + toAdd.value[i][j];
			
			return m_RetVal;
		}

		inline Matrix2 operator-(const Matrix2& toSubtract) const
		{
			Matrix2 m_RetVal;
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
					m_RetVal.value[i][j] = this->value[i][j] - toSubtract.value[i][j];
			return m_RetVal;
		}

		inline Matrix2 operator*(const real& multiplier) const
		{
			Matrix2 m_RetVal;
			for (byte i = 0; i < 2; i++)
			for (byte j = 0; j < 2; j++)
					m_RetVal.value[i][j] = this->value[i][j] * multiplier;
			return m_RetVal;
		}

		inline Vector2 operator*(Vector2& multiplier) const
		{
			return this->MultiplyByColumnVector(multiplier);
		}

		inline Matrix2 operator*(const Matrix2& multiplier) const
		{
			Matrix2 m_RetVal;

			m_RetVal.value[0][0] = (this->value[0][0] * multiplier.value[0][0]) + (this->value[0][1] * multiplier.value[1][0]);
			m_RetVal.value[0][1] = (this->value[0][0] * multiplier.value[0][1]) + (this->value[0][1] * multiplier.value[1][1]);
			m_RetVal.value[1][0] = (this->value[1][0] * multiplier.value[0][0]) + (this->value[1][1] * multiplier.value[1][0]);
			m_RetVal.value[1][1] = (this->value[1][0] * multiplier.value[0][1]) + (this->value[1][1] * multiplier.value[1][1]);

			return m_RetVal;
		}

		inline Matrix2 operator*=(const real& multiplier)
		{
			*this = *this * multiplier;
			return *this;
		}

		inline Matrix2 operator-(void) const
		{
			real determinant = this->Determinant();
			
			if (!determinant) return *this; // Non-invertible matrix detected.
	
			Matrix2 m_RetVal;
			m_RetVal.value[0][0] = this->value[1][1];
			m_RetVal.value[1][1] = this->value[0][0];
			m_RetVal.value[0][1] = this->value[0][1] * -1;
			m_RetVal.value[1][0] = this->value[1][0] * -1;
			
			m_RetVal *= determinant;
		}

		/* Common matrix operations implementations.*/
		
		inline void Identity(void)
		{
			this->value[0][0] = 1;
			this->value[0][1] = 0;
			this->value[1][0] = 0;
			this->value[1][1] = 1;
		}

		inline void Transpose(void)
		{
			real pt_Temp[2];

			pt_Temp[0] = this->value[0][1];
			pt_Temp[1] = this->value[1][0];

			this->value[1][0] = pt_Temp[0];
			this->value[0][1] = pt_Temp[1];
		}
		
		inline real Trace(void) const
		{
			real trace = 0;
			for (byte i = 0; i < 2; i++)
				trace += this->value[i][i];
			return trace;
		}

		inline real Determinant(void) const
		{
			return (this->value[0][0] * this->value[1][1]) - (this->value[0][1] * this->value[0][1]);
		}

		inline Vector2 MultiplyByColumnVector(const Vector2& multiplier) const
		{
			Vector2 v_RetVal;
			v_RetVal.i = (this->value[0][0] * multiplier.i) + (this->value[0][1] * multiplier.j);
			v_RetVal.j = (this->value[1][0] * multiplier.i) + (this->value[1][1] * multiplier.j);
			return v_RetVal;
		}
	};
}